package com.zero.manage.web.config.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import com.zero.manage.web.components.dto.LoginDto;
import com.zero.manage.web.components.entity.SysLogEntity;
import com.zero.manage.web.components.service.SysLogService;
import io.gitee.zerowsh.config.Utils;
import io.gitee.zerowsh.constant.ToolConstant;
import io.gitee.zerowsh.enums.ResultEnums;
import io.gitee.zerowsh.enums.ToolEnums;
import io.gitee.zerowsh.util.JwtUtil;
import io.gitee.zerowsh.util.PublicResult;
import io.gitee.zerowsh.util.RsaUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 系统日志切面
 */
@Aspect  // 使用@Aspect注解声明一个切面
@Component
@Log4j2
public class LogAspect {
    @Autowired
    private SysLogService sysLogService;
    private ThreadLocal<SysLogEntity> threadLocalLogEntity = new ThreadLocal<>();
    private Map<Object, Object> dataMap = new LinkedHashMap<>();
    @Autowired
    private HttpServletRequest request;

    @Value("${rsa-private-key}")
    private String rsaPrivateKey;

    //表示匹配com.savage.server包及其子包下的所有方法
//    @Pointcut("execution(* com.wsh.zero.controller..*.*(..))")
    @Pointcut("@annotation(io.gitee.zerowsh.util.LogTag)")
    public void pointcut() {
    }


//    //前置通知
//    @Before("pointcut()")
//    public void beforeMethod(JoinPoint joinPoint) {
//        System.out.println("调用了前置通知");
//
//    }
//
//    //@After: 后置通知
//    @After("pointcut()")
//    public void afterMethod(JoinPoint joinPoint) {
//        System.out.println("调用了后置通知");
//    }
//
//    //@AfterRunning: 返回通知 rsult为返回内容
//    @AfterReturning(value = "pointcut()", returning = "result")
//    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
//        System.out.println("调用了返回通知");
//    }

    //@AfterThrowing: 异常通知
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterReturningMethod(JoinPoint joinPoint, Exception e) {
        //当controller没有捕获到异常时会进入
        SysLogEntity sysLogEntity = threadLocalLogEntity.get();
        if (Objects.isNull(sysLogEntity) || StrUtil.isBlank(sysLogEntity.getId())) {
            setEntity(joinPoint);
        }
        dataMap.put("response", e);
        sysLogEntity.setData(JSONUtil.toJsonStr(dataMap));
        sysLogEntity.setLevel(ToolEnums.LogLevelEnums.ERROR);
        sysLogService.add(sysLogEntity);

    }

    //@Around：环绕通知
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        setEntity(pjp);
        Object object = pjp.proceed();
        SysLogEntity sysLogEntity = threadLocalLogEntity.get();
        try {
            PublicResult publicResult = JSONUtil.toBean(JSONUtil.toJsonStr(object), PublicResult.class);
            int code = publicResult.getCode();
            /**
             * 200==info
             * 400 || 401 || 404==warning
             * 500 == error
             */
            if (ResultEnums.OK.getKey() == code) {
                sysLogEntity.setLevel(ToolEnums.LogLevelEnums.INFO);
            } else if (ResultEnums.FAIL.getKey() == code) {
                sysLogEntity.setLevel(ToolEnums.LogLevelEnums.WARN);
            } else {
                sysLogEntity.setLevel(ToolEnums.LogLevelEnums.ERROR);
            }
        } catch (Exception e) {
            log.error(e);
            sysLogEntity.setLevel(ToolEnums.LogLevelEnums.ERROR);
        }

        dataMap.put("response", object);
        sysLogEntity.setData(JSONUtil.toJsonStr(dataMap));
        sysLogService.add(sysLogEntity);
        return object;
    }


    public void setEntity(JoinPoint pjp) {
        SysLogEntity entity = new SysLogEntity();
        String classMethod = pjp.getTarget().getClass().getName() + "." + pjp.getSignature().getName();
        //获取传入目标方法的参数
        Object[] args = pjp.getArgs();
        StringBuilder params = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            params.append(args[i]);
            if (i < args.length - 1) {
                params.append(",");
            }
        }
        String token = request.getHeader(ToolConstant.ACCESS_TOKEN_NAME);
        if (StrUtil.isBlank(token)) {
            token = request.getParameter(ToolConstant.ACCESS_TOKEN_NAME);
        }
        String name;

        if (StrUtil.isNotBlank(token)) {
            try {
                name = JwtUtil.getClaim(token, ToolConstant.MAME);
            } catch (Exception e) {
                log.warn("token获取用户信息异常：" + token);
                name = "用户名异常";
            }
        } else {
            //构建成登录信息并取用户名
            LoginDto loginDto = JSONUtil.toBean(JSONUtil.toJsonStr(args[0]), LoginDto.class);
            name = RsaUtil.decrypt(loginDto.getName(), rsaPrivateKey);
        }
        entity.setUsername(name);
        entity.setMethod(request.getMethod() + "：" + classMethod);
        entity.setRequestAddress(Utils.getRealIp() + request.getRequestURI());
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (Objects.nonNull(requestAttributes)) {
            HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
            UserAgent ua = UserAgentUtil.parse(httpServletRequest.getHeader("User-Agent"));
            dataMap.put("browser", ua.getBrowser().toString());
            dataMap.put("platform", ua.getPlatform().getName());
        }
        /*
         * 通过注解获取标题和操作
         * 标题从controller的@Api tags第一个元素获取
         * 操作从 方法上@ApiOperation  value值获取
         */
        //入参 要扫描的包名
        Reflections f = new Reflections(pjp.getTarget().getClass().getName());
        //入参 目标注解类
        Set<Class<?>> set = f.getTypesAnnotatedWith(Api.class);
        //这里集合只会获取到一个
        List<Class<?>> list = new ArrayList<>(set);
        Class<?> aClass = list.get(0);
        Api api = aClass.getAnnotation(Api.class);
        if (null != api) {
            entity.setTitle(api.tags()[0]);
        }
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        if (null != apiOperation) {
            //注解上的描述
            entity.setOperation(apiOperation.value());
        }
        entity.setCreateTime(System.currentTimeMillis());
        dataMap.put("params", params);
        threadLocalLogEntity.set(entity);
    }
}
